/*
************************************************************************************************************************
*                                                      uC/OS-III
*                                                 The Real-Time Kernel
*
*                                  (c) Copyright 2009-2014; Micrium, Inc.; Weston, FL
*                           All rights reserved.  Protected by international copyright laws.
*
*                                                   TIME MANAGEMENT
*
* File    : OS_TIME.C
* By      : JJL
* Version : V3.04.04
*
* LICENSING TERMS:
* ---------------
*           uC/OS-III is provided in source form for FREE short-term evaluation, for educational use or
*           for peaceful research.  If you plan or intend to use uC/OS-III in a commercial application/
*           product then, you need to contact Micrium to properly license uC/OS-III for its use in your
*           application/product.   We provide ALL the source code for your convenience and to help you
*           experience uC/OS-III.  The fact that the source is provided does NOT mean that you can use
*           it commercially without paying a licensing fee.
*
*           Knowledge of the source code may NOT be used to develop a similar product.
*
*           Please help us continue to provide the embedded community with the finest software available.
*           Your honesty is greatly appreciated.
*
*           You can find our product's user manual, API reference, release notes and
*           more information at https://doc.micrium.com.
*           You can contact us at www.micrium.com.
************************************************************************************************************************
*/

#define  MICRIUM_SOURCE
#include "os.h"

#ifdef VSC_INCLUDE_SOURCE_FILE_NAMES
const  CPU_CHAR  *os_time__c = "$Id: $";
#endif

/*
************************************************************************************************************************
*                                                  DELAY TASK 'n' TICKS
*
* Description: This function is called to delay execution of the currently running task until the specified number of
*              system ticks expires.  This, of course, directly equates to delaying the current task for some time to
*              expire.  No delay will result if the specified delay is 0.  If the specified delay is greater than 0
*              then, a context switch will result.
*
* Arguments  : dly       is a value in 'clock ticks' that the task will either delay for or, the target match value
*                        of the tick counter (OSTickCtr).  Note that specifying 0 means the task is not to delay.
*
*                        depending on the option argument, the task will wake up when OSTickCtr reaches:
*
*                            OS_OPT_TIME_DLY      : OSTickCtr + dly
*                            OS_OPT_TIME_TIMEOUT  : OSTickCtr + dly
*                            OS_OPT_TIME_MATCH    : dly
*                            OS_OPT_TIME_PERIODIC : OSTCBCurPtr.TickCtrPrev + dly
*
*              opt       specifies whether 'dly' represents absolute or relative time; default option marked with *** :
*
*                        *** OS_OPT_TIME_DLY        specifies a relative time from the current value of OSTickCtr.
*                            OS_OPT_TIME_TIMEOUT    same as OS_OPT_TIME_DLY.
*                            OS_OPT_TIME_MATCH      indicates that 'dly' specifies the absolute value that OSTickCtr
*                                                   must reach before the task will be resumed.
*                            OS_OPT_TIME_PERIODIC   indicates that 'dly' specifies the periodic value that OSTickCtr
*                                                   must reach before the task will be resumed.
*
*              p_err     is a pointer to a variable that will contain an error code from this call.
*
*                            OS_ERR_NONE            the call was successful and the delay occurred.
*                            OS_ERR_OPT_INVALID     if you specified an invalid option for this function.
*                            OS_ERR_SCHED_LOCKED    can't delay when the scheduler is locked.
*                            OS_ERR_TIME_DLY_ISR    if you called this function from an ISR.
*                            OS_ERR_TIME_ZERO_DLY   if you specified a delay of zero.
*
* Returns    : none
************************************************************************************************************************
*/

void  OSTimeDly(OS_TICK   dly,
                OS_OPT    opt,
                OS_ERR   *p_err)
{
    CPU_SR_ALLOC();
#ifdef OS_SAFETY_CRITICAL

    if(p_err == (OS_ERR *)0)
    {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }

#endif
#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u

    if(OSIntNestingCtr > (OS_NESTING_CTR)0u)                /* Not allowed to call from an ISR                        */
    {
        *p_err = OS_ERR_TIME_DLY_ISR;
        return;
    }

#endif

    if(OSSchedLockNestingCtr > (OS_NESTING_CTR)0u)          /* Can't delay when the scheduler is locked               */
    {
        *p_err = OS_ERR_SCHED_LOCKED;
        return;
    }

    switch(opt)
    {
        case OS_OPT_TIME_DLY:
        case OS_OPT_TIME_TIMEOUT:
        case OS_OPT_TIME_PERIODIC:
            if(dly == (OS_TICK)0u)                         /* 0 means no delay!                                      */
            {
                *p_err = OS_ERR_TIME_ZERO_DLY;
                return;
            }

            break;

        case OS_OPT_TIME_MATCH:
            break;

        default:
            *p_err = OS_ERR_OPT_INVALID;
            return;
    }

    OS_CRITICAL_ENTER();
    OS_TickListInsertDly(OSTCBCurPtr,
                         dly,
                         opt,
                         p_err);

    if(*p_err != OS_ERR_NONE)
    {
        OS_CRITICAL_EXIT_NO_SCHED();
        return;
    }

#if (defined(TRACE_CFG_EN) && (TRACE_CFG_EN > 0u))
    TRACE_OS_TASK_DLY(dly);                                 /* Record the event.                                      */
#endif
    OS_RdyListRemove(OSTCBCurPtr);                          /* Remove current task from ready list                    */
    OS_CRITICAL_EXIT_NO_SCHED();
    OSSched();                                              /* Find next task to run!                                 */
    *p_err = OS_ERR_NONE;
}


/*
************************************************************************************************************************
*                                             DELAY TASK FOR SPECIFIED TIME
*
* Description: This function is called to delay execution of the currently running task until some time expires.  This
*              call allows you to specify the delay time in HOURS, MINUTES, SECONDS and MILLISECONDS instead of ticks.
*
* Arguments  : hours     specifies the number of hours that the task will be delayed (max. is 999 if the tick rate is
*                        1000 Hz or less otherwise, a higher value would overflow a 32-bit unsigned counter).
*
*              minutes   specifies the number of minutes (max. 59 if 'opt' is OS_OPT_TIME_HMSM_STRICT)
*
*              seconds   specifies the number of seconds (max. 59 if 'opt' is OS_OPT_TIME_HMSM_STRICT)
*
*              milli     specifies the number of milliseconds (max. 999 if 'opt' is OS_OPT_TIME_HMSM_STRICT)
*
*              opt       specifies time delay bit-field options logically OR'd; default options marked with *** :
*
*                        *** OS_OPT_TIME_DLY        specifies a relative time from the current value of OSTickCtr.
*                            OS_OPT_TIME_TIMEOUT    same as OS_OPT_TIME_DLY.
*                            OS_OPT_TIME_MATCH      indicates that the delay specifies the absolute value that OSTickCtr
*                                                   must reach before the task will be resumed.
*                            OS_OPT_TIME_PERIODIC   indicates that the delay specifies the periodic value that OSTickCtr
*                                                   must reach before the task will be resumed.
*
*                        *** OS_OPT_TIME_HMSM_STRICT            strictly allow only hours        (0...99)
*                                                                                   minutes      (0...59)
*                                                                                   seconds      (0...59)
*                                                                                   milliseconds (0...999)
*                            OS_OPT_TIME_HMSM_NON_STRICT        allow any value of  hours        (0...999)
*                                                                                   minutes      (0...9999)
*                                                                                   seconds      (0...65535)
*                                                                                   milliseconds (0...4294967295)
*
*              p_err     is a pointer to a variable that will receive an error code from this call.
*
*                            OS_ERR_NONE                        If the function returns from the desired delay
*                            OS_ERR_OPT_INVALID                 If you specified an invalid option for 'opt'
*                            OS_ERR_SCHED_LOCKED                Can't delay when the scheduler is locked
*                            OS_ERR_TIME_DLY_ISR                If called from an ISR
*                            OS_ERR_TIME_INVALID_HOURS          If you didn't specify a valid value for 'hours'
*                            OS_ERR_TIME_INVALID_MINUTES        If you didn't specify a valid value for 'minutes'
*                            OS_ERR_TIME_INVALID_SECONDS        If you didn't specify a valid value for 'seconds'
*                            OS_ERR_TIME_INVALID_MILLISECONDS   If you didn't specify a valid value for 'milli'
*                            OS_ERR_TIME_ZERO_DLY               If hours, minutes, seconds and milli are all 0
*
* Returns    : none
*
* Note(s)    : 1) The resolution on the milliseconds depends on the tick rate.  For example, you can't do a 10 mS delay
*                 if the ticker interrupts every 100 mS.  In this case, the delay would be set to 0.  The actual delay
*                 is rounded to the nearest tick.
*
*              2) Although this function allows you to delay a task for many, many hours, it's not recommended to put
*                 a task to sleep for that long.
************************************************************************************************************************
*/

#if OS_CFG_TIME_DLY_HMSM_EN > 0u
void  OSTimeDlyHMSM(CPU_INT16U   hours,
                    CPU_INT16U   minutes,
                    CPU_INT16U   seconds,
                    CPU_INT32U   milli,
                    OS_OPT       opt,
                    OS_ERR      *p_err)
{
#if OS_CFG_ARG_CHK_EN > 0u
    CPU_BOOLEAN  opt_invalid;
    CPU_BOOLEAN  opt_non_strict;
#endif
    OS_OPT       opt_time;
    OS_RATE_HZ   tick_rate;
    OS_TICK      ticks;
    CPU_SR_ALLOC();
#ifdef OS_SAFETY_CRITICAL

    if(p_err == (OS_ERR *)0)
    {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }

#endif
#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u

    if(OSIntNestingCtr > (OS_NESTING_CTR)0u)                /* Not allowed to call from an ISR                        */
    {
        *p_err = OS_ERR_TIME_DLY_ISR;
        return;
    }

#endif

    if(OSSchedLockNestingCtr > (OS_NESTING_CTR)0u)          /* Can't delay when the scheduler is locked               */
    {
        *p_err = OS_ERR_SCHED_LOCKED;
        return;
    }

    opt_time = opt & OS_OPT_TIME_MASK;                      /* Retrieve time options only.                            */

    switch(opt_time)
    {
        case OS_OPT_TIME_DLY:
        case OS_OPT_TIME_TIMEOUT:
        case OS_OPT_TIME_PERIODIC:
            if(milli == (CPU_INT32U)0u)                    /* Make sure we didn't specify a 0 delay                  */
            {
                if(seconds == (CPU_INT16U)0u)
                {
                    if(minutes == (CPU_INT16U)0u)
                    {
                        if(hours == (CPU_INT16U)0u)
                        {
                            *p_err = OS_ERR_TIME_ZERO_DLY;
                            return;
                        }
                    }
                }
            }

            break;

        case OS_OPT_TIME_MATCH:
            break;

        default:
            *p_err = OS_ERR_OPT_INVALID;
            return;
    }

#if OS_CFG_ARG_CHK_EN > 0u                                  /* Validate arguments to be within range                  */
    opt_invalid = DEF_BIT_IS_SET_ANY(opt, ~OS_OPT_TIME_OPTS_MASK);

    if(opt_invalid == DEF_YES)
    {
        *p_err = OS_ERR_OPT_INVALID;
        return;
    }

    opt_non_strict = DEF_BIT_IS_SET(opt, OS_OPT_TIME_HMSM_NON_STRICT);

    if(opt_non_strict != DEF_YES)
    {
        if(milli   > (CPU_INT32U)999u)
        {
            *p_err = OS_ERR_TIME_INVALID_MILLISECONDS;
            return;
        }

        if(seconds > (CPU_INT16U)59u)
        {
            *p_err = OS_ERR_TIME_INVALID_SECONDS;
            return;
        }

        if(minutes > (CPU_INT16U)59u)
        {
            *p_err = OS_ERR_TIME_INVALID_MINUTES;
            return;
        }

        if(hours   > (CPU_INT16U)99u)
        {
            *p_err = OS_ERR_TIME_INVALID_HOURS;
            return;
        }
    }
    else
    {
        if(minutes > (CPU_INT16U)9999u)
        {
            *p_err = OS_ERR_TIME_INVALID_MINUTES;
            return;
        }

        if(hours   > (CPU_INT16U)999u)
        {
            *p_err = OS_ERR_TIME_INVALID_HOURS;
            return;
        }
    }

#endif
    /* Compute the total number of clock ticks required..     */
    /* .. (rounded to the nearest tick)                       */
    tick_rate = OSCfg_TickRate_Hz;
    ticks     = ((OS_TICK)hours * (OS_TICK)3600u + (OS_TICK)minutes * (OS_TICK)60u + (OS_TICK)seconds) * tick_rate
                + (tick_rate * ((OS_TICK)milli + (OS_TICK)500u / tick_rate)) / (OS_TICK)1000u;

    if(ticks > (OS_TICK)0u)
    {
        OS_CRITICAL_ENTER();
        OS_TickListInsertDly(OSTCBCurPtr,
                             ticks,
                             opt_time,
                             p_err);

        if(*p_err != OS_ERR_NONE)
        {
            OS_CRITICAL_EXIT_NO_SCHED();
            return;
        }

#if (defined(TRACE_CFG_EN) && (TRACE_CFG_EN > 0u))
        TRACE_OS_TASK_DLY(ticks);                           /* Record the event.                                      */
#endif
        OS_RdyListRemove(OSTCBCurPtr);                      /* Remove current task from ready list                    */
        OS_CRITICAL_EXIT_NO_SCHED();
        OSSched();                                          /* Find next task to run!                                 */
        *p_err = OS_ERR_NONE;
    }
    else
    {
        *p_err = OS_ERR_TIME_ZERO_DLY;
    }
}
#endif

/*
************************************************************************************************************************
*                                                RESUME A DELAYED TASK
*
* Description: This function is used resume a task that has been delayed through a call to either OSTimeDly() or
*              OSTimeDlyHMSM().  Note that you cannot call this function to resume a task that is waiting for an event
*              with timeout.
*
* Arguments  : p_tcb    is a pointer to the TCB of the task to resume.
*
*              p_err    is a pointer to a variable that will receive an error code
*
*                           OS_ERR_NONE                  Task has been resumed
*                           OS_ERR_STATE_INVALID         Task is in an invalid state
*                           OS_ERR_TIME_DLY_RESUME_ISR   If called from an ISR
*                           OS_ERR_TIME_NOT_DLY          Task is not waiting for time to expire
*                           OS_ERR_TASK_SUSPENDED        Task cannot be resumed, it was suspended by OSTaskSuspend()
*
* Note(s)    : none
************************************************************************************************************************
*/

#if OS_CFG_TIME_DLY_RESUME_EN > 0u
void  OSTimeDlyResume(OS_TCB  *p_tcb,
                      OS_ERR  *p_err)
{
    CPU_SR_ALLOC();
#ifdef OS_SAFETY_CRITICAL

    if(p_err == (OS_ERR *)0)
    {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }

#endif
#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u

    if(OSIntNestingCtr > (OS_NESTING_CTR)0u)                /* Not allowed to call from an ISR                        */
    {
        *p_err = OS_ERR_TIME_DLY_RESUME_ISR;
        return;
    }

#endif
#if OS_CFG_ARG_CHK_EN > 0u

    if(p_tcb == (OS_TCB *)0)                                /* Not possible for the running task to be delayed!       */
    {
        *p_err = OS_ERR_TASK_NOT_DLY;
        return;
    }

#endif
    CPU_CRITICAL_ENTER();

    if(p_tcb == OSTCBCurPtr)                                /* Not possible for the running task to be delayed!       */
    {
        *p_err = OS_ERR_TASK_NOT_DLY;
        CPU_CRITICAL_EXIT();
        return;
    }

    switch(p_tcb->TaskState)
    {
        case OS_TASK_STATE_RDY:                             /* Cannot Abort delay if task is ready                    */
            CPU_CRITICAL_EXIT();
            *p_err = OS_ERR_TASK_NOT_DLY;
            break;

        case OS_TASK_STATE_DLY:
            OS_CRITICAL_ENTER_CPU_EXIT();
            p_tcb->TaskState = OS_TASK_STATE_RDY;
            OS_TickListRemove(p_tcb);                      /* Remove task from tick list                             */
            OS_RdyListInsert(p_tcb);                       /* Add to ready list                                      */
            OS_CRITICAL_EXIT_NO_SCHED();
            *p_err = OS_ERR_NONE;
            break;

        case OS_TASK_STATE_PEND:
            CPU_CRITICAL_EXIT();
            *p_err = OS_ERR_TASK_NOT_DLY;
            break;

        case OS_TASK_STATE_PEND_TIMEOUT:
            CPU_CRITICAL_EXIT();
            *p_err = OS_ERR_TASK_NOT_DLY;
            break;

        case OS_TASK_STATE_SUSPENDED:
            CPU_CRITICAL_EXIT();
            *p_err = OS_ERR_TASK_NOT_DLY;
            break;

        case OS_TASK_STATE_DLY_SUSPENDED:
            OS_CRITICAL_ENTER_CPU_EXIT();
            p_tcb->TaskState = OS_TASK_STATE_SUSPENDED;
            OS_TickListRemove(p_tcb);                      /* Remove task from tick list                             */
            OS_CRITICAL_EXIT_NO_SCHED();
            *p_err            = OS_ERR_TASK_SUSPENDED;
            break;

        case OS_TASK_STATE_PEND_SUSPENDED:
            CPU_CRITICAL_EXIT();
            *p_err = OS_ERR_TASK_NOT_DLY;
            break;

        case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
            CPU_CRITICAL_EXIT();
            *p_err = OS_ERR_TASK_NOT_DLY;
            break;

        default:
            CPU_CRITICAL_EXIT();
            *p_err = OS_ERR_STATE_INVALID;
            break;
    }

    OSSched();
}
#endif

/*
************************************************************************************************************************
*                                               GET CURRENT SYSTEM TIME
*
* Description: This function is used by your application to obtain the current value of the counter which keeps track of
*              the number of clock ticks.
*
* Arguments  : p_err    is a pointer to a variable that will receive an error code
*
*                           OS_ERR_NONE           If the call was successful
*
* Returns    : The current value of OSTickCtr
************************************************************************************************************************
*/

OS_TICK  OSTimeGet(OS_ERR  *p_err)
{
    OS_TICK  ticks;
    CPU_SR_ALLOC();
#ifdef OS_SAFETY_CRITICAL

    if(p_err == (OS_ERR *)0)
    {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return ((OS_TICK)0);
    }

#endif
    CPU_CRITICAL_ENTER();
    ticks = OSTickCtr;
    CPU_CRITICAL_EXIT();
    *p_err = OS_ERR_NONE;
    return (ticks);
}

/*
************************************************************************************************************************
*                                                   SET SYSTEM CLOCK
*
* Description: This function sets the counter which keeps track of the number of clock ticks.
*
* Arguments  : ticks    is the desired tick value
*
*              p_err    is a pointer to a variable that will receive an error code
*
*                           OS_ERR_NONE           If the call was successful
*
* Returns    : none
************************************************************************************************************************
*/

void  OSTimeSet(OS_TICK   ticks,
                OS_ERR   *p_err)
{
    CPU_SR_ALLOC();
#ifdef OS_SAFETY_CRITICAL

    if(p_err == (OS_ERR *)0)
    {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }

#endif
    CPU_CRITICAL_ENTER();
    OSTickCtr = ticks;
    CPU_CRITICAL_EXIT();
    *p_err     = OS_ERR_NONE;
}


/*
************************************************************************************************************************
*                                                 PROCESS SYSTEM TICK
*
* Description: This function is used to signal to uC/OS-III the occurrence of a 'system tick' (also known as a
*              'clock tick').  This function should be called by the tick ISR.
*
* Arguments  : none
*
* Returns    : none
************************************************************************************************************************
*/

void  OSTimeTick(void)
{
    OS_ERR  err;
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
    CPU_TS  ts;
#endif
    OSTimeTickHook();                                       /* Call user definable hook                               */
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
    ts = OS_TS_GET();                                       /* Get timestamp                                          */
    OS_IntQPost((OS_OBJ_TYPE) OS_OBJ_TYPE_TICK,             /* Post to ISR queue                                      */
                (void      *)&OSRdyList[OSPrioCur],
                (void      *) 0,
                (OS_MSG_SIZE) 0u,
                (OS_FLAGS) 0u,
                (OS_OPT) 0u,
                (CPU_TS) ts,
                (OS_ERR    *)&err);
#else
    (void)OSTaskSemPost((OS_TCB *)&OSTickTaskTCB,            /* Signal tick task                                       */
                        (OS_OPT) OS_OPT_POST_NONE,
                        (OS_ERR *)&err);
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
    OS_SchedRoundRobin(&OSRdyList[OSPrioCur]);
#endif
#if OS_CFG_TMR_EN > 0u
    OSTmrUpdateCtr--;

    if(OSTmrUpdateCtr == (OS_CTR)0u)
    {
        OSTmrUpdateCtr = OSTmrUpdateCnt;
        OSTaskSemPost((OS_TCB *)&OSTmrTaskTCB,              /* Signal timer task                                      */
                      (OS_OPT) OS_OPT_POST_NONE,
                      (OS_ERR *)&err);
    }

#endif
#endif
}
